package ws.utils.thread;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadFactory;

/**
 * This class provides services to create, start threads and wait for them
 * to finish execution.
 * 
 * @author Wilson Santos, 36228 <wilson.santos@ua.pt>
 */
public class ThreadManager implements ThreadFactory {

  /** List with the managed threads. */
  private List<Thread> threadList;
  /** Maximum number of threads that can be created. */
  private int maxThreadCount;
  /** Thread are created with this priority. */
  private int threadPriority;

  /**
   * Create a new instance.
   * Threads aren't initialized. It is just allocated space for them.
   * Threads will be create with priority {@code Thread.MAX_PRIORITY}.
   *
   * @param numThreads number of threads to be created.
   */
  public ThreadManager(int numberOfThreads) throws InstantiationException, IllegalAccessException {
    this(numberOfThreads, Thread.MAX_PRIORITY);
  }

  /**
   * Create a new instance.
   * Threads aren't initialized. It is just allocated space for them.
   *
   * @param numThreads number of threads to be created.
   */
  public ThreadManager(int numberOfThreads, int priority) throws InstantiationException, IllegalAccessException {
    threadList = new ArrayList<Thread>(numberOfThreads);
    maxThreadCount = numberOfThreads;
    threadPriority = priority;
  }

  /**
   * Create a new instance.
   * Threads are initialized with a new instance of the {@code Runnable task} and {@code Thread.MAX_PRIORITY}.
   * 
   * @param numThreads number of threads to be created.
   * @param task task that will be executed by the created threads.
   */
  public ThreadManager(int numberOfThreads, Class<? extends Runnable> task) throws InstantiationException, IllegalAccessException {
    this(numberOfThreads, task, Thread.MAX_PRIORITY);
  }

  /**
   * Create a new instance.
   * Threads are initialized with a new instance of the {@code Runnable task}.
   *
   * @param numThreads number of threads to be created.
   * @param task task that will be executed by the created threads.
   * @param priority thread priority for the created threads.
   */
  public ThreadManager(int numberOfThreads, Class<? extends Runnable> task, int priority) throws InstantiationException, IllegalAccessException {
    this(numberOfThreads, priority);

    for (int i = 0; i < numberOfThreads; ++i) {
      Thread t = new Thread(task.newInstance());
      t.setPriority(priority);
      threadList.add(t);
    }
  }

  /**
   * Create a new instance.
   * Threads are initialized with the {@code Runnable task} and {@code Thread.MAX_PRIORITY}..
   *
   * @param numThreads number of threads to be created.
   * @param task task that will be executed by the created threads.
   */
  public ThreadManager(int numberOfThreads, Runnable task) throws InstantiationException, IllegalAccessException {
    this(numberOfThreads, task, Thread.MAX_PRIORITY);
  }

  /**
   * Create a new instance.
   * Threads are initialized with the {@code Runnable task}.
   *
   * @param numThreads number of threads to be created.
   * @param task task that will be executed by the created threads.
   */
  public ThreadManager(int numberOfThreads, Runnable task, int priority) throws InstantiationException, IllegalAccessException {
    this(numberOfThreads, priority);

    for (int i = 0; i < numberOfThreads; ++i) {
      Thread t = new Thread(task);
      t.setPriority(Thread.MAX_PRIORITY);
      threadList.add(t);
    }
  }

  /**
   * Start the threads on the thread list.
   */
  public void startThreads() {
    for (Thread t : threadList) {
      t.start();
    }
  }

  /**
   * Wait for the threads on the list to finish.
   *
   * @throws InterruptedException
   */
  public void waitForThreads() throws InterruptedException {
    for (Thread t : threadList) {
      try {
        t.join();
      } catch (InterruptedException ex) {
        System.out.println("ThreadManager interrupted.");
        return;
      }
    }
  }

  /**
   * Constructs a new {@code Thread} and adds it the the internal thread list.
   *
   * @param r a runnable to be executed by new thread instance.
   *
   * @return constructed thread, or {@code null} if the request to create a thread is rejected.
   * This may happen if this manager have already created the maximum number of threads.
   */
  public Thread newThread(Runnable r) {
    if (threadList.size() == maxThreadCount) {
      return null;
    }

    Thread ret = new Thread(r);
    ret.setPriority(threadPriority);

    threadList.add(ret);

    return ret;
  }

  public void interrupt() {
    for (Thread t : threadList) {
      t.interrupt();
    }
  }
}
